//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Apache License, Version 2.0.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************

namespace Microsoft.Research.ScientificWorkflow.UIDesigner
{
    #region NameSpace
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.ComponentModel;
    using System.Globalization;
    using System.Linq;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Documents;
    using System.Windows.Input;
    using Microsoft.Research.ScientificWorkflow.TridentComposer;
    using Microsoft.Research.ScientificWorkflow.TridentModel;
    using Microsoft.Research.ScientificWorkflow.TridentUtilities;
    using Microsoft.Research.DataLayer;
    using Microsoft.Research.eResearch.Common.Linq;
    using TridentAPI;
    #endregion

    /// <summary>
    /// Interaction logic for ActivityTree.xaml
    /// </summary>
    public partial class ActivityTree : UserControl, IDisposable
    {
        #region Declaration

        /// <summary>
        /// Event declaration to handle MouseDown Event
        /// </summary>
        public event MouseButtonEventHandler OnActivityMouseDown;

        /// <summary>
        /// Holds the Child Node Style
        /// </summary>
        private Style childStyle;

        /// <summary>
        /// Holds paraent Node Style
        /// </summary>
        private Style parentStyle;

        /// <summary>
        /// Holds the datasource for Activity Tree
        /// </summary>
        private Collection<ActivityCategoryModel> categoryList;

        /// <summary>
        /// Holds the categories read from XML Categories.xml
        /// </summary>
        private Collection<ActivityCategoryModel> categoryXmlList;

        /// <summary>
        /// Seed for the automation ID.
        /// </summary>
        private static int nodeNameInteger;

        /// <summary>
        /// The registry connection.
        /// </summary>
        private Connection registryConnection;

        /// <summary>
        /// The background worker for the activity retrieval.
        /// </summary>
        private BackgroundWorker activityRetriever;

        /// <summary>
        /// The activities which are the older versions and are present in the workflow.
        /// </summary>
        private Collection<BaseModel> oldModels;

        /// <summary>
        /// A list of categories currently expanded.
        /// </summary>
        private List<Guid> expandedCategories = new List<Guid>();

        /// <summary>
        /// The treeview items to expand post refresh.
        /// </summary>
        private List<TreeViewItem> categoriesToExpand = new List<TreeViewItem>();

        /// <summary>
        /// A flag to specify if the worker thread is busy.
        /// </summary>
        private bool workerBusy;

        /// <summary>
        /// An event to notify the Ui that the Activity load has completed.
        /// </summary>
        public event EventHandler ActivitiesLoaded;

        /// <summary>
        /// An event to notify to the Ui that activity download has started.
        /// </summary>
        public event EventHandler CategoryExpanding;

        private const string DUMMYNODESTRING = "dummy";

        #endregion

        #region Constructor

        /// <summary>
        /// Constructor : Builds the Activity Tree based on NamespaceCollection
        /// </summary>
        /// <param name="paramcategoryList">List of category models</param>
        /// <param name="connection">The registry connection.</param>
        public ActivityTree(Collection<ActivityCategoryModel> parameterCategoryList, Connection connection)
        {
            if (parameterCategoryList == null)
            {
                throw new ArgumentNullException("parameterCategoryList");
            }

            try
            {
                InitializeComponent();

                this.categoryList = parameterCategoryList;
                this.registryConnection = connection.Clone();
                this.registryConnection.Open();

                // Initialize the background worker thread.
                activityRetriever = new BackgroundWorker();
                activityRetriever.DoWork += new DoWorkEventHandler(activityRetriever_DoWork);
                activityRetriever.RunWorkerCompleted += new RunWorkerCompletedEventHandler(ActivityRetrievalCompleted);

                this.GetChildItemStyle();
                this.GetParentItemStyle();

                // Load the user categories from the local xml.
                this.LoadCategoriesFromXML();

                // Build the activity tree.
                this.BuildActivityTree(new Collection<BaseModel>());
            }
            catch (TridentCustomException exception)
            {
                TridentErrorHandler.HandleTridentCustomExceptionInUI(exception);
            }
            catch (Exception exception)
            {
                TridentErrorHandler.HandleKnownExceptionsInUI(exception);
            }
        }

        #endregion

        #region Public methods
        
        /// <summary>
        /// Refresh the activity tree.
        /// </summary>
        /// <param name="paramcategoryList"></param>
        public void RefreshTree(Collection<ActivityCategoryModel> parameterCategoryList, Collection<BaseModel> oldModels)
        {
            try
            {
                this.activityTreeView.Items.Clear();
                this.categoriesToExpand.Clear();
                this.categoryList = parameterCategoryList;

                this.BuildActivityTree(oldModels);
                this.ExpandActivities();
            }
            catch (TridentCustomException exception)
            {
                TridentErrorHandler.HandleTridentCustomExceptionInUI(exception);
            }
            catch (Exception exception)
            {
                TridentErrorHandler.HandleKnownExceptionsInUI(exception);
            }
        }

        #endregion

        #region Private methods
        
        /// <summary>
        /// Get the Image path from xml file
        /// </summary>
        /// <param name="paramCategoryName">paramCategoryName</param>
        /// <returns>string Image path</returns>
        private static string GetImagePath(string parameterCategoryName, Collection<ActivityCategoryModel> listToBeSearched)
        {
            if (null != listToBeSearched)
            {
                foreach (ActivityCategoryModel category in listToBeSearched)
                {
                    if (string.Equals(category.Path, parameterCategoryName))
                    {
                        listToBeSearched = category.Categories;
                        return category.IconImageSource.ToString();
                    }
                }
            }
            return @"/Microsoft.Research.ScientificWorkflow.TridentUtilities;component/Images/ScientificWorkflow/ProcessIcon.png";
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="listToBeSearched"></param>
        /// <param name="name"></param>
        /// <returns></returns>
        private static Collection<ActivityCategoryModel> GetActivityCategoryList(Collection<ActivityCategoryModel> listToBeSearched, string name)
        {
            if (null != listToBeSearched)
            {
                foreach (ActivityCategoryModel category in listToBeSearched)
                {
                    if (string.Equals(category.Path, name))
                    {
                        return category.Categories;
                    }
                }
            }
            return listToBeSearched;
        }

        /// <summary>
        /// Set the Image based on the category Name
        /// </summary>
        /// <param name="categoryName"></param>
        /// <returns></returns>
        private static string SetImageForCategory(string categoryName)
        {
            switch (categoryName)
            {
                case UIDesignerCodeConstants.UNCATEGORY:
                    return @"/Microsoft.Research.ScientificWorkflow.TridentUtilities;component/Images/ScientificWorkflow/ControlFlow.png";
                case UIDesignerCodeConstants.DATAIO:
                    return @"/Microsoft.Research.ScientificWorkflow.TridentUtilities;component/Images/ScientificWorkflow/DataStoreICon.png";
                case UIDesignerCodeConstants.VISUALIZATION:
                    return @"/Microsoft.Research.ScientificWorkflow.TridentUtilities;component/Images/ScientificWorkflow/ProcessIcon.png";
                case UIDesignerCodeConstants.CONTROLFLOW:
                    return @"/Microsoft.Research.ScientificWorkflow.TridentUtilities;component/Images/ScientificWorkflow/VisualizationIcon.png";
                case UIDesignerCodeConstants.DATAPROCESSING:
                    return @"/Microsoft.Research.ScientificWorkflow.TridentUtilities;component/Images/ScientificWorkflow/ProcessIcon.png";
                default:
                    return @"/Microsoft.Research.ScientificWorkflow.TridentUtilities;component/Images/ScientificWorkflow/ControlFlow.png";
            }

        }

        /// <summary>
        /// Generates the Stack Panel with name and Description
        /// </summary>
        /// <param name="name">string</param>
        /// <param name="description">string</param>
        /// <returns></returns>
        private static TextBlock GenerateToolTip(string paramName, string paramDescription)
        {
            TextBlock toolTipTxtBlk = new TextBlock();
            toolTipTxtBlk.TextWrapping = TextWrapping.Wrap;
            toolTipTxtBlk.MaxWidth = 500;
            Run nameLabelBlockRun = new Run(UIDesignerCodeConstants.ACTIVITYTOOLTIPNAME);
            nameLabelBlockRun.FontWeight = FontWeights.Bold;
            Run nameTextBlockRun = new Run(paramName);
            Run newLineRun = new Run(Environment.NewLine);
            Run descriptionLabelBlockRun = new Run(UIDesignerCodeConstants.ACTIVITYTOOLTIPDESCRIPTION);
            descriptionLabelBlockRun.FontWeight = FontWeights.Bold;
            Run descriptionTextBlockRun = new Run(paramDescription);

            toolTipTxtBlk.Inlines.Add(nameLabelBlockRun);
            toolTipTxtBlk.Inlines.Add(nameTextBlockRun);
            toolTipTxtBlk.Inlines.Add(newLineRun);
            toolTipTxtBlk.Inlines.Add(descriptionLabelBlockRun);
            toolTipTxtBlk.Inlines.Add(descriptionTextBlockRun);

            return toolTipTxtBlk;
        }

        /// <summary>
        /// Loads the categories from the Xml
        /// </summary>
        private void LoadCategoriesFromXML()
        {
            this.categoryXmlList = CategoriesComposer.ReadAllCategories();
        }

        /// <summary>
        /// Method builds the Activity Tree by iterating the Namespace list
        /// </summary>
        private void BuildActivityTree(Collection<BaseModel> oldModels)
        {
            IOrderedEnumerable<ActivityCategoryModel> categoryModels = this.categoryList.OrderBy(item => item.Label);
            foreach(ActivityCategoryModel objCategory in categoryModels)
            {
                TreeViewItem parentNode = this.BuildCategoryNode(objCategory, this.categoryXmlList, oldModels);
                this.activityTreeView.Items.Add(parentNode);
                this.oldModels = oldModels;
            }
        }

        /// <summary>
        /// Builds the Category Node fo the given ActivityCategoryModel
        /// </summary>
        /// <param name="category">ActivityCategoryModel</param>
        /// <returns>TreeViewItem</returns>
        private TreeViewItem BuildCategoryNode(ActivityCategoryModel category, Collection<ActivityCategoryModel> activityCategoryList, Collection<BaseModel> oldModels)
        {
            TreeViewItem categoryParentNode = new TreeViewItem();
            categoryParentNode.Margin = new Thickness(0, 3, 0, 0);
            categoryParentNode.Header = category.Name;
            categoryParentNode.Tag = ActivityTree.GetImagePath(category.Name, activityCategoryList);
            categoryParentNode.Style = this.parentStyle;
            categoryParentNode.Expanded += new RoutedEventHandler(OnCategoryExpand);
            categoryParentNode.Collapsed += new RoutedEventHandler(OnCategoryCollapse);
            categoryParentNode.DataContext = category;

            // Set the Name for the Treeview Item - Required for Automation
            categoryParentNode.Name = "CategoryNode" + nodeNameInteger.ToString(CultureInfo.InvariantCulture);

            // Increment the counter
            nodeNameInteger = nodeNameInteger + 1;

            if (this.expandedCategories.Contains(category.Id))
            {
                this.categoriesToExpand.Add(categoryParentNode);
            }

            // Iterate through the sub categories and build a tree for them.
            for (int categoryCounter = 0; categoryCounter < category.Categories.Count; categoryCounter++)
            {
                Collection<ActivityCategoryModel> listToBeSearched = ActivityTree.GetActivityCategoryList(activityCategoryList, category.Name);
                TreeViewItem childItem = this.BuildCategoryNode(category.Categories[categoryCounter], listToBeSearched, oldModels);
                categoryParentNode.Items.Add(childItem);
            }

            // Add a dummy node to the tree to simulate the presence of a child. The activities will be loaded when the tree is expanded.
            if (category.Categories.Count == 0)
            {
                // Add activities to the list.
                TreeViewItem dummyNode = new TreeViewItem();
                dummyNode.Tag = DUMMYNODESTRING;
                categoryParentNode.Items.Add(dummyNode);
            }

            // the node created.
            return categoryParentNode;
        }

        /// <summary>
        /// Builds the Activity Node
        /// Get called BuildCategoryNode Method
        /// </summary>
        /// <param name="activityModel">ActivityModel</param>
        /// <returns>TreeViewItem</returns>
        private TreeViewItem BuildActivityNode(ActivityMetadata activityMetadata)
        {
            TreeViewItem activityParentNode = null;

            if (null != activityMetadata)
            {
                if (activityMetadata.IsHidden || activityMetadata.IsDeleted)
                {
                    return null;
                }
                activityParentNode = new TreeViewItem();
                activityParentNode.Margin = new Thickness(0, 3, 0, 0);
                activityParentNode.Tag = ActivityTree.SetImageForCategory(string.Empty);
                string label = activityMetadata.Label;
                if (string.IsNullOrEmpty(label))
                {
                    label = activityMetadata.Name;
                }
                activityParentNode.Header = label;
                activityParentNode.ToolTip = ActivityTree.GenerateToolTip(activityMetadata.Name, activityMetadata.Description);
                activityParentNode.DataContext = activityMetadata;

                // Set the Name for the Treeview Item - Required for Automation
                activityParentNode.Name = "ActivityNode" + nodeNameInteger.ToString(CultureInfo.InvariantCulture);
                nodeNameInteger = nodeNameInteger + 1;

                // Add the mouse down event handler to the node.
                activityParentNode.PreviewMouseDown += new MouseButtonEventHandler(ActivityPreviewMouseLeftButtonDown);
                activityParentNode.Style = this.childStyle;
            }

            // Return the created node.
            return activityParentNode;
        }

        /// <summary>
        /// Adds to the category the activities, version wise.
        /// </summary>
        /// <param name="category">The category.</param>
        /// <param name="categoryParentNode">The category parent node.</param>
        /// <param name="modelsIdHash">The models id hash.</param>
        private static IEnumerable<ActivityMetadata> ActivityVersionHelper(ActivityCategoryModel category, Collection<BaseModel> oldModels)
        {
            return category.Activities
                .Where(model => ActivityTree.IdentifyIfAppropriateActivity(model, oldModels));
        }

        /// <summary>
        /// Identifies if appropriate activity.
        /// </summary>
        /// <param name="metadata">The metadata.</param>
        /// <param name="oldModels">The old models.</param>
        /// <returns></returns>
        private static bool IdentifyIfAppropriateActivity(ActivityMetadata metadata, Collection<BaseModel> oldModels)
        {
            bool requiredActivity = false;
            if (metadata != null
                && ((null != oldModels.FirstOrDefault(model => model.Id.Equals(metadata.Id)))
                    || ((metadata.LatestEntityId.Equals(Guid.Empty) || metadata.Id.Equals(metadata.LatestEntityId))
                        && null == oldModels.FirstOrDefault(model => model.LatestEntityId.Equals(metadata.Id)))))
            {
                requiredActivity = true;
            }

            return requiredActivity;
        }

        /// <summary>
        /// Expands the activities post refresh.
        /// </summary>
        private void ExpandActivities()
        {
            if (this.categoriesToExpand.Count > 0)
            {
                TreeViewItem itemToExpand = this.categoriesToExpand[0];
                this.categoriesToExpand.RemoveAt(0);
                if (!itemToExpand.IsExpanded)
                {
                    itemToExpand.IsExpanded = true;
                }
                else
                {
                    if (this.categoriesToExpand.Count == 0)
                    {
                        this.ExpandCompleted();
                    }
                }
            }
            else
            {
                this.ExpandCompleted();
            }
        }

        /// <summary>
        /// 
        /// </summary>
        private void ExpandCompleted()
        {
            this.Dispatcher.BeginInvoke(new System.Action(delegate()
            {
                this.Cursor = null;
                if (ActivitiesLoaded != null)
                {
                    ActivitiesLoaded.Invoke(this, new EventArgs());
                }
            }));
        }

        /// <summary>
        /// Get the Child Node Style
        /// </summary>
        private void GetChildItemStyle()
        {
            this.childStyle = (Style)TryFindResource(UIDesignerCodeConstants.CHILDACTIVITYTREEITEMSTYLE);
        }

        /// <summary>
        /// Get the Parent Node Style
        /// </summary>
        private void GetParentItemStyle()
        {
            this.parentStyle = (Style)TryFindResource(UIDesignerCodeConstants.PARENTACTIVITYTREEITEMSTYLE);
        }


        #endregion

        #region Private event handlers
        /// <summary>
        /// The expanded event handler for the category tree item.
        /// </summary>
        /// <param name="sender">The origin of the event.</param>
        /// <param name="e">The event args.</param>
        private void OnCategoryExpand(object sender, RoutedEventArgs e)
        {
            try
            {
                TreeViewItem treeViewItem = sender as TreeViewItem;

                if (!this.workerBusy)
                {
                    ActivityCategoryModel expandedCategory = treeViewItem.DataContext as ActivityCategoryModel;

                    if (!this.expandedCategories.Contains(expandedCategory.Id))
                    {
                        this.expandedCategories.Add(expandedCategory.Id);
                    }
                    if (treeViewItem.Items.Count == 1 && (treeViewItem.Items[0] as TreeViewItem).Tag.Equals(DUMMYNODESTRING))
                    {
                        treeViewItem.Items.Clear();
                    }
                    if (!expandedCategory.ActivitiesLoaded)
                    {
                        if (this.CategoryExpanding != null)
                        {
                            this.CategoryExpanding.Invoke(this, new EventArgs());
                        }
                        this.Cursor = Cursors.Wait;
                        this.workerBusy = true;
                        this.activityRetriever.RunWorkerAsync(new CategoryData() { ParentCategoryModel = expandedCategory, ParentTreeViewItem = treeViewItem });
                        expandedCategory.ActivitiesLoaded = true;
                    }
                    else
                    {
                        this.ExpandActivities();
                    }
                }
                else
                {
                    this.categoriesToExpand.Add(treeViewItem);
                }
                e.Handled = true;
            }
            catch (TridentCustomException exception)
            {
                TridentErrorHandler.HandleTridentCustomExceptionInUI(exception);
            }
            catch (Exception exception)
            {
                TridentErrorHandler.HandleKnownExceptionsInUI(exception);
            }
        }

        /// <summary>
        /// The collapsed event handler for the category tree item.
        /// </summary>
        /// <param name="sender">The origin of the event.</param>
        /// <param name="e">The event args.</param>
        void OnCategoryCollapse(object sender, RoutedEventArgs e)
        {
            TreeViewItem treeViewItem = sender as TreeViewItem;
            ActivityCategoryModel collapsedCategory = treeViewItem.DataContext as ActivityCategoryModel;
            this.expandedCategories.Remove(collapsedCategory.Id);
            e.Handled = true;
        }

        /// <summary>
        /// The method which is executed on the background thread.
        /// </summary>
        /// <param name="sender">The origin of the event.</param>
        /// <param name="e">The event args.</param>
        private void activityRetriever_DoWork(object sender, DoWorkEventArgs e)
        {
            try
            {
                CategoryData categoryData = e.Argument as CategoryData;
                TreeViewItem category = categoryData.ParentTreeViewItem;
                ActivityCategoryModel expandedCategory = categoryData.ParentCategoryModel;
                Collection<ActivityMetadata> activities = CategoriesComposer.GetActivitiesFromCategory(expandedCategory.Id, this.registryConnection);
                if (activities != null)
                {
                    activities.ForEach(a => expandedCategory.Activities.Add(a));
                    IEnumerable<ActivityMetadata> activitiesToAdd = ActivityTree.ActivityVersionHelper(expandedCategory, oldModels);
                    this.Dispatcher.BeginInvoke(new System.Action(delegate()
                    {
                        activitiesToAdd = activitiesToAdd.OrderBy(item => !string.IsNullOrEmpty(item.Label) ? item.Label : item.Name);
                        foreach (ActivityMetadata metadata in activitiesToAdd)
                        {
                            if (!metadata.IsHidden && !metadata.IsDeleted)
                            {
                                category.Items.Add(this.BuildActivityNode(metadata));
                            }
                        }
                    }));
                }
            }
            catch (TridentCustomException exception)
            {
                TridentErrorHandler.HandleTridentCustomExceptionInUI(exception);
            }
            catch (Exception exception)
            {
                TridentErrorHandler.HandleKnownExceptionsInUI(exception);
            }
        }

        /// <summary>
        /// Event handler for the background thread completed method.
        /// </summary>
        /// <param name="sender">The origin of the event.</param>
        /// <param name="e">The event args.</param>
        private void ActivityRetrievalCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            this.workerBusy = false;
            if (e.Error != null)
            {
                TridentErrorHandler.HandleAndThrowBusinessLogicException(e.Error,
                    TridentErrorConstants.ErrorNumber90000035,
                    TridentResourceManager.GetString("ActivityRetrieveError"));

                this.categoriesToExpand.Clear();
            }
            this.ExpandActivities();
        }


        /// <summary>
        /// Event handler for mouse left button down
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ActivityPreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            if (OnActivityMouseDown != null)
            {
                this.OnActivityMouseDown.Invoke(sender, e);
            }
        }

        #endregion

        private class CategoryData
        {
            public TreeViewItem ParentTreeViewItem { get; set; }
            public ActivityCategoryModel ParentCategoryModel { get; set; }
        }

        #region IDisposable Members
        
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        // The bulk of the clean-up code is implemented in Dispose(bool)
        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (this.registryConnection != null)
                {
                    this.registryConnection = null;
                }

                if (this.activityRetriever != null)
                {
                    this.activityRetriever.Dispose();
                }
            }
        }

        #endregion
    }
}
